﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using MultiResolutioning.Data;
using MultiResolutioning.Structures;

namespace MultiResolutioning.Files
{
	public static class PLYMeshFile
	{
		private struct Element
		{
			public string name;
			public int count;
			public List<string> properties;

			public Element(string name, int count)
			{
				this.name = name;
				this.count = count;
				properties = new List<string>();
			}
		}

		/// <summary>
		/// Wczytuje plik PLY i zwraca go jako string.
		/// </summary>
		/// <param name="path">Sciezka.</param>
		public static string LoadFromFile(string path)
		{
			StreamReader reader = new StreamReader(path);
			return reader.ReadToEnd();
		}

		/// <summary>
		/// Wczytuje plik PLY do struktury.
		/// </summary>
		/// <param name="path">Sciezka.</param>
		public static MeshData LoadFromPLYFile(string path)
		{
			char[] emptySpace = { ' ' };
			
			string input;
			string[] inputSplitted;
			List<Element> elements = new List<Element>();
			StreamReader reader = new StreamReader(path);
			
			input = reader.ReadLine();
			
			// check if the file is proper.
			if (input != "ply") throw new Exception("This is not a .ply file.");

			// reading rest of file header.
			do
			{
				input = reader.ReadLine();
				inputSplitted = input.Split(emptySpace, StringSplitOptions.RemoveEmptyEntries);

				if (inputSplitted[0] == "element")
				{
					elements.Add(new Element(inputSplitted[1], int.Parse(inputSplitted[2])));
				}
				else if (inputSplitted[0] == "property")
				{
					elements[elements.Count - 1].properties.Add(input);
				}

			} while (input != "end_header");

			MeshData dataStructure = new MeshData();

			foreach (Element element in elements)
			{
				if (element.name == "vertex")
				{
					dataStructure.vertexPropertiesNames = element.properties;
					dataStructure.vertices = new List<Vertex>(element.count);

					CultureInfo ciDotSeparator = (CultureInfo)CultureInfo.CurrentCulture.Clone();
					ciDotSeparator.NumberFormat.NumberDecimalSeparator = ".";

					for (int i = 0; i < element.count; ++i)
					{
						input = reader.ReadLine();
						inputSplitted = input.Split(emptySpace);
						Vertex tempVertex = new Vertex(
							float.Parse(inputSplitted[0], ciDotSeparator),
							float.Parse(inputSplitted[1], ciDotSeparator),
							float.Parse(inputSplitted[2], ciDotSeparator));

						tempVertex.vertexId = dataStructure.GetNewVertexId();

						dataStructure.vertices.Add(tempVertex);
					}
				}
				else if (element.name == "face")
				{
					dataStructure.faces = new List<Face>(element.count);
					int vertexId;

					for (int i = 0; i < element.count; ++i)
					{
						input = reader.ReadLine();
						inputSplitted = input.Split(emptySpace);
						Face tempFace = new Face();

						vertexId = int.Parse(inputSplitted[1]);
						tempFace.vertices[0] = dataStructure.vertices[vertexId];
						dataStructure.vertices[vertexId].faces.Add(tempFace);

						vertexId = int.Parse(inputSplitted[2]);
						tempFace.vertices[1] = dataStructure.vertices[vertexId];
						dataStructure.vertices[vertexId].faces.Add(tempFace);

						vertexId = int.Parse(inputSplitted[3]);
						tempFace.vertices[2] = dataStructure.vertices[vertexId];
						dataStructure.vertices[vertexId].faces.Add(tempFace);

						tempFace.vertices[0].neighbors.Add(tempFace.vertices[1]);
						tempFace.vertices[0].neighbors.Add(tempFace.vertices[2]);

						tempFace.vertices[1].neighbors.Add(tempFace.vertices[0]);
						tempFace.vertices[1].neighbors.Add(tempFace.vertices[2]);

						tempFace.vertices[2].neighbors.Add(tempFace.vertices[0]);
						tempFace.vertices[2].neighbors.Add(tempFace.vertices[1]);

						foreach (Vertex v in tempFace.vertices)
						{
							v.neighbors = v.neighbors.Distinct().ToList();
						}

						tempFace.faceId = dataStructure.GetNewFaceId();
						dataStructure.faces.Add(tempFace);
					}
				}
			}

			reader.Close();

			return dataStructure;
		}

		/// <summary>
		/// Zapisuje stan struktury do pliku PLY.
		/// </summary>
		/// <param name="mesh">Siatka.</param>
		/// <param name="fileName">Nazwa pliku.</param>
		public static void SaveToPLYFile(MeshData mesh, string fileName)
		{
			StreamWriter sw = new StreamWriter(fileName, false);

			sw.WriteLine("ply");
			sw.WriteLine("format ascii 1.0 ");
			sw.Write("element vertex {0}\n", mesh.vertices.Count());
			sw.WriteLine("property float x");
			sw.WriteLine("property float y");
			sw.WriteLine("property float z");
			sw.Write("element face {0}\n", mesh.faces.Count());
			sw.WriteLine("property list uchar int vertex_indices");
			sw.WriteLine("end_header");

			CultureInfo ciDotSeparator = (CultureInfo)CultureInfo.CurrentCulture.Clone();
			ciDotSeparator.NumberFormat.NumberDecimalSeparator = ".";

			int i = 0;
			foreach (Vertex vertex in mesh.vertices)
			{
				sw.Write(vertex.position.X.ToString(ciDotSeparator) + " " +
								 vertex.position.Y.ToString(ciDotSeparator) + " " +
								 vertex.position.Z.ToString(ciDotSeparator) + "\n");
				
				mesh.vertices[i].saveId = i;
				++i;
			}

			foreach (Face face in mesh.faces)
			{
				sw.Write(
					"3 " + face.vertices[0].saveId + " " +
					face.vertices[1].saveId + " " +
					face.vertices[2].saveId + "\n");
			}

			sw.Close();
		}

		
	}
}
